use super::{Error, Result, Value};

impl Value {
    pub fn op_add(self, rhs: Self) -> Result<Self> {
        const ERR: &str = "int, real, string or list";
        match self {
            Self::Integer(l) => match rhs {
                Self::Integer(r) => Ok(Self::Integer(l + r)),
                Self::Real(r) => Ok(Self::Real(l as f32 + r)),
                Self::String(r) => Ok(Self::String(format!("{l}{r}"))),
                Self::List(mut r) => {
                    r.push(l.into());
                    Ok(Self::List(r))
                }
                _ => Err(Error::TypeMismatch {
                    expect: ERR,
                    get: rhs.type_name(),
                }),
            },
            Self::Real(l) => match rhs {
                Self::Integer(r) => Ok(Self::Real(l + r as f32)),
                Self::Real(r) => Ok(Self::Real(l + r)),
                Self::String(r) => Ok(Self::String(format!("{l}{r}"))),
                Self::List(mut r) => {
                    r.push(l.into());
                    Ok(Self::List(r))
                }
                _ => Err(Error::TypeMismatch {
                    expect: ERR,
                    get: rhs.type_name(),
                }),
            },
            Self::String(l) => match rhs {
                Self::Integer(r) => Ok(Self::String(format!("{l}{r}"))),
                Self::Real(r) => Ok(Self::String(format!("{l}{r}"))),
                Self::String(r) => Ok(Self::String(format!("{l}{r}"))),
                Self::List(mut r) => {
                    r.push(l.into());
                    Ok(Self::List(r))
                }
                _ => Err(Error::TypeMismatch {
                    expect: ERR,
                    get: rhs.type_name(),
                }),
            },
            Self::List(mut l) => match rhs {
                Self::List(mut rl) => {
                    rl.append(&mut l);
                    Ok(Value::List(rl))
                }
                v => {
                    l.push(v);
                    Ok(Value::List(l))
                }
            },
            _ => Err(Error::TypeMismatch {
                expect: ERR,
                get: self.type_name(),
            }),
        }
    }

    pub fn op_sub(self, rhs: Self) -> Result<Self> {
        match self {
            Self::Integer(l) => match rhs {
                Self::Integer(r) => Ok(Self::Integer(l - r)),
                Self::Real(r) => Ok(Self::Real(l as f32 - r)),
                _ => Err(Error::TypeMismatch {
                    expect: "int or real",
                    get: rhs.type_name(),
                }),
            },
            Self::Real(l) => match rhs {
                Self::Integer(r) => Ok(Self::Real(l - r as f32)),
                Self::Real(r) => Ok(Self::Real(l - r)),
                _ => Err(Error::TypeMismatch {
                    expect: "int or real",
                    get: rhs.type_name(),
                }),
            },
            _ => Err(Error::TypeMismatch {
                expect: "int or real",
                get: self.type_name(),
            }),
        }
    }

    pub fn op_mul(self, rhs: Self) -> Result<Self> {
        match self {
            Self::Integer(l) => match rhs {
                Self::Integer(r) => Ok(Self::Integer(l * r)),
                Self::Real(r) => Ok(Self::Real(l as f32 * r)),
                _ => Err(Error::TypeMismatch {
                    expect: "int or real",
                    get: rhs.type_name(),
                }),
            },
            Self::Real(l) => match rhs {
                Self::Integer(r) => Ok(Self::Real(l * r as f32)),
                Self::Real(r) => Ok(Self::Real(l * r)),
                _ => Err(Error::TypeMismatch {
                    expect: "int or real",
                    get: rhs.type_name(),
                }),
            },
            _ => Err(Error::TypeMismatch {
                expect: "int or real",
                get: self.type_name(),
            }),
        }
    }

    pub fn op_div(self, rhs: Self) -> Result<Self> {
        match self {
            Self::Integer(l) => match rhs {
                Self::Integer(r) => Ok(Self::Integer(l / r)),
                Self::Real(r) => Ok(Self::Real(l as f32 / r)),
                _ => Err(Error::TypeMismatch {
                    expect: "int or real",
                    get: rhs.type_name(),
                }),
            },
            Self::Real(l) => match rhs {
                Self::Integer(r) => Ok(Self::Real(l / r as f32)),
                Self::Real(r) => Ok(Self::Real(l / r)),
                _ => Err(Error::TypeMismatch {
                    expect: "int or real",
                    get: rhs.type_name(),
                }),
            },
            _ => Err(Error::TypeMismatch {
                expect: "int or real",
                get: self.type_name(),
            }),
        }
    }

    pub fn at(self, rhs: Self) -> Result<Self> {
        match self {
            Self::List(l) => {
                if let Value::Integer(idx) = rhs {
                    if idx < 0 {
                        Err(Error::IndexOutOfRange {
                            idx,
                            target: 0..l.len(),
                        })
                    } else if let Some(v) = l.get(idx as usize) {
                        Ok(v.clone())
                    } else {
                        Err(Error::IndexOutOfRange {
                            idx,
                            target: 0..l.len(),
                        })
                    }
                } else {
                    Err(Error::OperationNotSupported {
                        op: "@",
                        which: rhs.type_name(),
                    })
                }
            }
            _ => Err(Error::OperationNotSupported {
                op: "@",
                which: self.type_name(),
            }),
        }
    }

    pub fn neg(&self) -> Result<Self> {
        match self {
            Self::Integer(v) => Ok(Self::Integer(-v)),
            Self::Real(v) => Ok(Self::Real(-v)),
            _ => Err(Error::OperationNotSupported {
                op: "-",
                which: self.type_name(),
            }),
        }
    }

    pub fn op_shl(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Integer(lhs), Self::Integer(rhs)) => Ok(Self::Integer(
                lhs.checked_shl(rhs as u32)
                    .ok_or(Error::IntegerOverflow(lhs))?,
            )),
            (Self::Integer(_), rhs) => Err(Error::OperationNotSupported {
                op: "<<",
                which: rhs.type_name(),
            }),
            (lhs, _) => Err(Error::OperationNotSupported {
                op: "<<",
                which: lhs.type_name(),
            }),
        }
    }

    pub fn op_shr(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Integer(lhs), Self::Integer(rhs)) => Ok(Self::Integer(
                lhs.checked_shr(rhs as u32)
                    .ok_or(Error::IntegerOverflow(lhs))?,
            )),
            (Self::Integer(_), rhs) => Err(Error::OperationNotSupported {
                op: ">>",
                which: rhs.type_name(),
            }),
            (lhs, _) => Err(Error::OperationNotSupported {
                op: ">>",
                which: lhs.type_name(),
            }),
        }
    }

    pub fn op_bitand(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Integer(lhs), Self::Integer(rhs)) => Ok(Self::Integer(lhs & rhs)),
            (Self::Integer(_), rhs) => Err(Error::OperationNotSupported {
                op: "&",
                which: rhs.type_name(),
            }),
            (lhs, _) => Err(Error::OperationNotSupported {
                op: "&",
                which: lhs.type_name(),
            }),
        }
    }

    pub fn op_bitor(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Integer(lhs), Self::Integer(rhs)) => Ok(Self::Integer(lhs | rhs)),
            (Self::Integer(_), rhs) => Err(Error::OperationNotSupported {
                op: "|",
                which: rhs.type_name(),
            }),
            (lhs, _) => Err(Error::OperationNotSupported {
                op: "|",
                which: lhs.type_name(),
            }),
        }
    }

    pub fn op_bitnot(self) -> Result<Self> {
        if let Self::Integer(v) = self {
            Ok(Self::Integer(!v))
        } else {
            Err(Error::OperationNotSupported {
                op: "!",
                which: self.type_name(),
            })
        }
    }

    pub fn and(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Boolean(lhs), Self::Boolean(rhs)) => Ok(Self::Boolean(lhs && rhs)),
            (Self::Boolean(_), rhs) => Err(Error::OperationNotSupported {
                op: "and",
                which: rhs.type_name(),
            }),
            (lhs, _) => Err(Error::OperationNotSupported {
                op: "and",
                which: lhs.type_name(),
            }),
        }
    }

    pub fn or(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Boolean(lhs), Self::Boolean(rhs)) => Ok(Self::Boolean(lhs || rhs)),
            (Self::Boolean(_), rhs) => Err(Error::OperationNotSupported {
                op: "or",
                which: rhs.type_name(),
            }),
            (lhs, _) => Err(Error::OperationNotSupported {
                op: "or",
                which: lhs.type_name(),
            }),
        }
    }

    pub fn op_not(self) -> Result<Self> {
        if let Self::Boolean(v) = self {
            Ok(Self::Boolean(!v))
        } else {
            Err(Error::OperationNotSupported {
                op: "not",
                which: self.type_name(),
            })
        }
    }

    pub fn eq(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Boolean(l), Self::Boolean(r)) => Ok(Self::Boolean(l == r)),
            (Self::Integer(l), Self::Integer(r)) => Ok(Self::Boolean(l == r)),
            (Self::Integer(l), Self::Real(r)) => {
                Ok(Self::Boolean((l as f32 - r).abs() < f32::EPSILON))
            }
            (Self::Real(l), Self::Integer(r)) => {
                Ok(Self::Boolean((l - r as f32).abs() < f32::EPSILON))
            }
            (Self::Real(l), Self::Real(r)) => Ok(Self::Boolean((l - r).abs() < f32::EPSILON)),
            (Self::String(l), Self::String(r)) => Ok(Self::Boolean(l == r)),
            (Self::BuiltinMacro(l), Self::BuiltinMacro(r)) => Ok(Self::Boolean(l == r)),
            _ => Ok(Self::Boolean(false)),
        }
    }

    pub fn lt(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Integer(l), Self::Integer(r)) => Ok(Self::Boolean(l < r)),
            (Self::Integer(l), Self::Real(r)) => Ok(Self::Boolean((l as f32) < r)),
            (Self::Real(l), Self::Integer(r)) => Ok(Self::Boolean(l < r as f32)),
            (Self::Real(l), Self::Real(r)) => Ok(Self::Boolean(l < r)),
            (lhs, Self::Integer(_)) | (lhs, Self::Real(_)) => Err(Error::OperationNotSupported {
                op: "<",
                which: lhs.type_name(),
            }),
            (_, rhs) => Err(Error::OperationNotSupported {
                op: "<",
                which: rhs.type_name(),
            }),
        }
    }

    pub fn gt(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Integer(l), Self::Integer(r)) => Ok(Self::Boolean(l > r)),
            (Self::Integer(l), Self::Real(r)) => Ok(Self::Boolean((l as f32) > r)),
            (Self::Real(l), Self::Integer(r)) => Ok(Self::Boolean(l > r as f32)),
            (Self::Real(l), Self::Real(r)) => Ok(Self::Boolean(l > r)),
            (lhs, Self::Integer(_)) | (lhs, Self::Real(_)) => Err(Error::OperationNotSupported {
                op: ">",
                which: lhs.type_name(),
            }),
            (_, rhs) => Err(Error::OperationNotSupported {
                op: ">",
                which: rhs.type_name(),
            }),
        }
    }

    pub fn le(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Integer(l), Self::Integer(r)) => Ok(Self::Boolean(l <= r)),
            (Self::Integer(l), Self::Real(r)) => Ok(Self::Boolean((l as f32) <= r)),
            (Self::Real(l), Self::Integer(r)) => Ok(Self::Boolean(l <= r as f32)),
            (Self::Real(l), Self::Real(r)) => Ok(Self::Boolean(l <= r)),
            (lhs, Self::Integer(_)) | (lhs, Self::Real(_)) => Err(Error::OperationNotSupported {
                op: "<=",
                which: lhs.type_name(),
            }),
            (_, rhs) => Err(Error::OperationNotSupported {
                op: "<=",
                which: rhs.type_name(),
            }),
        }
    }

    pub fn ge(self, rhs: Self) -> Result<Self> {
        match (self, rhs) {
            (Self::Integer(l), Self::Integer(r)) => Ok(Self::Boolean(l >= r)),
            (Self::Integer(l), Self::Real(r)) => Ok(Self::Boolean((l as f32) >= r)),
            (Self::Real(l), Self::Integer(r)) => Ok(Self::Boolean(l >= r as f32)),
            (Self::Real(l), Self::Real(r)) => Ok(Self::Boolean(l >= r)),
            (lhs, Self::Integer(_)) | (lhs, Self::Real(_)) => Err(Error::OperationNotSupported {
                op: ">=",
                which: lhs.type_name(),
            }),
            (_, rhs) => Err(Error::OperationNotSupported {
                op: ">=",
                which: rhs.type_name(),
            }),
        }
    }

    pub fn car(self) -> Result<Self> {
        match self {
            Self::List(mut list) => {
                if let Some(val) = list.pop() {
                    Ok(val)
                } else {
                    Ok(Value::Nil)
                }
            }
            _ => Err(Error::OperationNotSupported {
                op: "car",
                which: self.type_name(),
            }),
        }
    }

    pub fn cdr(self) -> Result<Self> {
        match self {
            Self::List(mut list) => {
                let _ = list.pop();
                if list.is_empty() {
                    Ok(Value::Nil)
                } else {
                    Ok(Value::List(list))
                }
            }
            _ => Err(Error::OperationNotSupported {
                op: "cdr",
                which: self.type_name(),
            }),
        }
    }
}
